package net.sf.xoperator.blackboard;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openbbs.blackboard.EntryFilter;
import org.openbbs.blackboard.Zone;
import org.openbbs.blackboard.ZoneSelector;
import org.openbbs.blackboard.persistence.BlackboardMemory;
import org.openbbs.blackboard.persistence.BlackboardMemoryException;
import org.openbbs.blackboard.persistence.snapshot.SnapshottableMemory;

public class ThreadsafeTransientMemory implements BlackboardMemory,
		SnapshottableMemory, Serializable {
	//private Map<Zone, Set<Object>> zones = new ConcurrentHashMap<Zone, Set<Object>>();
	private Map<Zone, Set<Object>> zones = Collections.synchronizedMap(new HashMap<Zone, Set<Object>>());
	//private Map<Object, Zone> entries = new ConcurrentHashMap<Object, Zone>();

	private Log log = LogFactory.getLog(ThreadsafeTransientMemory.class);

	/**
	 * Remove all zones and entries from the memory. If this method fails, the
	 * state of the memory is undefined.
	 */
	public void clear() {
		this.zones.clear();
		//this.entries.clear();
		this.log.info("Blackboard Memory cleared");
	}

	/**
	 * @see BlackboardMemory#createZone(Zone)
	 */
	public void createZone(Zone zone) {
		Validate.notNull(zone, "zone must not be null");

		if (this.zones.containsKey(zone)) {
			throw new BlackboardMemoryException("zone " + zone
					+ " already exists");
		}
		this.zones.put(zone, new HashSet<Object>());
		this.log.debug("Created Zone: " + zone.name());
		this.log.debug("In the blackboard are the following zones: ");
		for (Zone zn : this.zones.keySet()) {
			this.log.debug(zn.name());
		}
	}

	/**
	 * @see BlackboardMemory#dropZone(Zone)
	 */
	public void dropZone(Zone zone) {
		Validate.notNull(zone, "zone must not be null");

		if (!this.zones.containsKey(zone)) {
			throw new BlackboardMemoryException("zone " + zone
					+ " does not exist");
		}
		this.zones.remove(zone);

//		for (Object entry : this.zones.remove(zone)) {
//			this.entries.remove(entry);
//		}
		this.log.debug("Dropped Zone: " + zone.name());
		this.log.debug("In the blackboard are the following zones: ");
		for (Zone zn : this.zones.keySet()) {
			this.log.debug(zn.name());
		}
	}

	/**
	 * @see BlackboardMemory#zoneExists(Zone)
	 */
	public boolean zoneExists(Zone zone) {
		return this.zones.containsKey(zone);
	}

	/**
	 * @see BlackboardMemory#getEntries(ZoneSelector, EntryFilter)
	 */
	public Iterator<Object> getEntries(ZoneSelector zoneSelector,
			EntryFilter entryFilter) {
		return new EntryIterator(zoneSelector, entryFilter);
	}

	/**
	 * @see BlackboardMemory#storeEntry(Zone, Object)
	 */
	public void storeEntry(Zone zone, Object entry) {
		Validate.notNull(entry, "cannot store null entry");
		Validate.notNull(zone, "cannot store entry in null-zone");

//		if (this.entries.containsKey(entry)) {
//			throw new BlackboardMemoryException("cannot store entry " + entry
//					+ " twice inside the same memory");
//		}

		Set<Object> objectsInZone = this.zones.get(zone);
		Validate.notNull(objectsInZone, "zone " + zone + " is unknown");
		objectsInZone.add(entry);
		//entries.put(entry, zone);
	}

	/**
	 * @see BlackboardMemory#entryExists(Object)
	 */
	public boolean entryExists(Object entry) {
		Validate.notNull(entry, "cannot check existence of a null-entry");
		for (Set<Object> zoneset : this.zones.values()) {
			if(zoneset.contains(entry)){
				return true;
			}
		}
		return false;
		//return this.entries.containsKey(entry);
	}

	/**
	 * @see BlackboardMemory#getZone(Object)
	 */
	public Zone getZone(Object entry) {
		Validate.notNull(entry, "cannot get the zone of a null-entry");
		for (Zone zone : this.zones.keySet()) {
			if(this.zones.get(zone).contains(entry)){
				return zone;
			}
		}
		return null;
		//return this.entries.get(entry);
	}

	/**
	 * @see BlackboardMemory#removeEntry(Object)
	 */
	public void removeEntry(Object entry) {
		Validate.notNull(entry, "cannot remove null-entry from memory");
		for (Set<Object> zoneset : this.zones.values()) {
			zoneset.remove(entry);

		}
//		//Zone zone = this.entries.remove(entry);
//		Validate.notNull(zone, "removed entry has no zone");
//		Validate.isTrue(this.zones.get(zone).remove(entry),
//				"failed to remove entry from zone");
	}

	/**
	 * @see SnapshottableMemory#getEntriesInZone(Zone)
	 */
	public Iterator<Object> getEntriesInZone(Zone zone) {
		Validate.notNull(zone);

		if (!this.zones.containsKey(zone)) {
			throw new BlackboardMemoryException("unknown zone " + zone);
		}

		return Collections.unmodifiableCollection(this.zones.get(zone))
				.iterator();
	}

	/**
	 * @see SnapshottableMemory#getZonesIterator()
	 */
	public Iterator<Zone> getZonesIterator() {
		return Collections.unmodifiableCollection(this.zones.keySet())
				.iterator();
	}

	/**
	 * @see SnapshottableMemory#restoreEntry(Object, Zone)
	 */
	public void restoreEntry(Object entry, Zone zone) {
		this.storeEntry(zone, entry);
	}

	/**
	 * @see SnapshottableMemory#restoreZone(Zone)
	 */
	public void restoreZone(Zone zone) {
		this.createZone(zone);
	}

	/**
	 * An Iterator over all entries which are matched by an EntryFilter and
	 * which reside a zone selected by a ZoneSelector.
	 */
	private class EntryIterator implements Iterator<Object> {
		private Iterator<Zone> zoneIterator = null;

		private Iterator<Object> entryIterator = null;

		private final EntryFilter entryFilter;

		private Object nextEntry = null;

		public EntryIterator(ZoneSelector zoneSelector, EntryFilter entryFilter) {
			Validate.notNull(entryFilter);
			this.zoneIterator = new ZoneIterator(zoneSelector);
			this.entryFilter = entryFilter;
		}

		public boolean hasNext() {
			if (this.nextEntry != null) {
				return true;
			}
			return this.updateNextEntry();
		}

		public Object next() {
			if (this.nextEntry == null) {
				if (!this.updateNextEntry()) {
					throw new IllegalStateException(
							"no more entries to iterate over");
				}
			}

			Validate.notNull(this.nextEntry, "nextEntry is not set");
			Object entry = this.nextEntry;
			this.nextEntry = null;
			return entry;
		}

		public void remove() {
			throw new UnsupportedOperationException(
					"remove is not supported by this iterator");
		}

		private boolean updateNextEntry() {
			this.nextEntry = null;

			if (this.entryIterator == null) {
				this.gotoNextZone();
			}

			while (this.entryIterator != null && this.nextEntry == null) {
				while (this.entryIterator.hasNext()) {
					Object candidate = this.entryIterator.next();
					if (this.entryFilter.selects(candidate)) {
						this.nextEntry = candidate;
						break;
					}
				}

				if (this.nextEntry == null) {
					this.gotoNextZone();
				}
			}

			return this.nextEntry != null;
		}

		private void gotoNextZone() {
			if (!this.zoneIterator.hasNext()) {
				this.entryIterator = null;
				return;
			}

			Zone nextZone = this.zoneIterator.next();
			this.entryIterator = ThreadsafeTransientMemory.this.zones.get(nextZone).iterator();
		}
	}

	/**
	 * An Iterator over all Zones which are selected by a ZoneSelector.
	 */
	private class ZoneIterator implements Iterator<Zone> {
		private final ZoneSelector zoneSelector;

		private final Iterator<Zone> allZonesIterator;

		private Zone nextZone = null;

		public ZoneIterator(ZoneSelector zoneSelector) {
			Validate.notNull(zoneSelector);
			this.zoneSelector = zoneSelector;
			this.allZonesIterator = ThreadsafeTransientMemory.this.zones.keySet().iterator();
		}

		public boolean hasNext() {
			if (this.nextZone != null) {
				return true;
			}
			return this.updateNextZone();
		}

		public Zone next() {
			if (this.nextZone == null) {
				if (!this.updateNextZone()) {
					throw new IllegalStateException(
							"no more zones to iterate over");
				}
			}

			Validate.notNull(this.nextZone, "nextZone is not set");
			Zone zone = this.nextZone;
			this.nextZone = null;
			return zone;
		}

		public void remove() {
			throw new UnsupportedOperationException(
					"remove is not supported by this iterator");
		}

		private boolean updateNextZone() {
			this.nextZone = null;
			while (this.allZonesIterator.hasNext()) {
				Zone candiate = this.allZonesIterator.next();
				if (this.zoneSelector.selects(candiate)) {
					this.nextZone = candiate;
					break;
				}
			}
			return this.nextZone != null;
		}
	}
}
